package com.thiswhyme.redis.lock.spel;

import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 带缓存的表达式解析器
 *
 * @author thiswhyme
 * @date 2022/10/26
 */
public class ExpressionEvaluator extends CachedExpressionEvaluator {

    private final Map<ExpressionKey, Expression> keyExpressionCache = new ConcurrentHashMap<>(64);
    private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);

    /**
     * 创建SpEL评估上下文
     *
     * @param method
     * @param args
     * @param targetClass
     * @param target
     * @return
     */
    public EvaluationContext createEvaluationContext(Method method, Object[] args, Class<?> targetClass, Object target) {
        Method targetMethod = getTargetMethod(targetClass, method);
        ExpressionRootObject root = new ExpressionRootObject(method, args, targetClass, target);
        return new MethodBasedEvaluationContext(root, targetMethod, args, this.getParameterNameDiscoverer());
    }

    /**
     * 解析key
     *
     * @param conditionExpression
     * @param elementKey
     * @param evalContext
     * @param returnClass
     * @return
     */
    public <T> T key(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> returnClass) {
        return getExpression(this.keyExpressionCache, elementKey, conditionExpression).getValue(evalContext, returnClass);
    }

    /**
     * 获取并缓存目标方法
     *
     * @param targetClass
     * @param method
     * @return
     */
    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        return this.targetMethodCache.computeIfAbsent(methodKey, key -> AopUtils.getMostSpecificMethod(method, targetClass));
    }
}
